#!/usr/bin/env perl
#
# @(#) share/tools/proclogcat.pl
#
# @see http://devtcg.blogspot.com/2010/04/logcat-improved.html
# @see https://github.com/jasta/android-dev-tools

###############################################################################
##
## Simple script designed to insert after "adb logcat" in a pipeline to track a
## specific package's logcat messages.
##
## I typically invoke this script as a function in my profile:
##
## function plogcat() {
##    adb logcat | proclogcat $* | coloredlogcat.py
## }
##
## Then run as:
##
## $ plogcat org.devtcg.five
##
###############################################################################

use strict;
use Data::Dumper;

###############################################################################

@ARGV > 0 or usage($0);

# We support tracking multiple process names.  Fill %trackingInfo keys with the
# process names, where the values are to be filled with the current pid for
# that process or -1 if it is presumed dead.
my $trackingInfo = { map { $_ => -1 } @ARGV };

# Flush all writes immediately.  This is necessary as we expect this script to
# be placed between two other programs in a pipe which outputs text very slowly
# (adb logcat outputs only when events happen), so it's rare to fill up the
# buffer quickly.  Without this, the normal buffering that occurs between piped
# programs not directly attached to a pty would prevent the user from seeing
# messages as they arrive.
$| = 1;

# Lookup the pids of the processes before we start.  From then on, rely on
# the ActivityManager to tell us as the processes dies and starts.
my $numPids = get_pids($trackingInfo);
if ($numPids == 0) {
	print "- waiting for process ", join(' or ', keys %$trackingInfo), " -\n";
}

while (<STDIN>) {
	my $line = $_;
	my ($level, $tag, $pid, $message) = $line =~
			m/^([A-Z])\/(.*?)\(\s*(\d+)\s*\): (.*)$/;

	chomp $message;

	if ($tag eq 'ActivityManager') {
		if ($message =~ m/^Start proc (.*?) .*?: pid=(\d+) /) {
			if (exists $trackingInfo->{$1}) {
				$trackingInfo->{$1} = $2;
				print $line;
			}
		} elsif ($message =~ m/Process (.*?) \(pid (\d+)\) has died./) {
			if (exists $trackingInfo->{$1}) {
				$trackingInfo->{$1} = -1;
				print $line;
			}
		}
	} elsif (in_list($pid, values %$trackingInfo)) {
		print $line;
	}
}

###############################################################################

sub in_list($@) {
	my $needle = shift;
	my @haystack = @_;

	foreach my $hay (@haystack) {
		if ($hay eq $needle) {
			return 1;
		}
	}
	return 0;
}

sub get_pids {
	my $info = shift;
	my @ps = qx{adb shell ps};
	if (@ps == 0) {
		return -1;
	}
	my @columns = split /\s+/, (shift @ps);

	# There's a "STATE" column slipped in between WCHAN and NAME that has no
	# room for a column...
	splice @columns, $#columns, 0, 'STATE';

	my $numFound = 0;

	foreach (@ps) {
		s/\s+$//;
		my @data = split /\s+/, $_, scalar @columns;
		my %row = map { $_ => (shift @data) } @columns;

		if (exists $info->{$row{NAME}}) {
			$info->{$row{NAME}} = $row{PID};
			$numFound++;
		}
	}

	return $numFound;
}

sub usage {
	my $prog = shift;
	die <<"EOF"
Usage: adb logcat | $0 <process-name>

Usually, `process-name' is usually the same as your package, but not
necessarily.  To make sure, type `adb shell ps' and look through the list.
EOF
}

